package spider

import (
	"asocrawler/conf"
	"asocrawler/db"
	"asocrawler/tool"
	"crypto/md5"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	RANK_URL_ONE   = "https://api-cn.play.heytapmobi.com/gamelist/tab/rank?size=100&rankId=10006&pageNo=0" //热门榜
	RANK_URL_TWO   = "https://api-cn.play.heytapmobi.com/gamelist/tab/rank?size=100&rankId=10009&pageNo=0" //新品榜
	RANK_URL_THREE = "https://api-cn.play.heytapmobi.com/gamelist/tab/rank?size=100&rankId=10015&pageNo=0" //网游榜

	T_TAG                   = "wp"
	HEADER_KEY_PID          = "pid"
	HEADER_KEY_USER_AGENT   = "User-Agent"
	HEADER_KEY_T            = "t"
	HEADER_KEY_ID           = "id"
	HEADER_KEY_OCS          = "ocs"
	HEADER_KEY_CH           = "ch"
	HEADER_KEY_OAK          = "oak"
	HEADER_KEY_LOCALE       = "locale"
	HEADER_KEY_SIGN         = "sign"
	HEADER_KEY_SG           = "sg"
	HEADER_KEY_INSTPLATCODE = "instPlatCode"

	HEADER_VALUE_PID        = "59"
	HEADER_VALUE_CH         = "3501"
	HEADER_VALUE_OAK        = "49ed82335ccdd661"
	HEADER_VALUE_LOCALE     = "zh-CN;CN"
	HEADER_VALUE_USER_AGENT = "OnePlus%2FGM1910%2F30%2F11%2F0%2F20162%2F3501%2F1"
	HEADER_VALUE_ID         = "/A6396861A4FCAA315AE637F7081BF52313EE43E98E4B70D4E8F853AD64FF57FF/14FF05DD4F4040B702C02B2BE04B1F36C68A35085E48406B0CC40DE63B145C17/"
	HEADER_VALUE_OCS        = "OnePlus%2FGM1910%2F30%2F11%2F0%2F20162%2FGM1910_21_211221%2F1"

	SIGN_KEY1                 = "49ed82335ccdd6616ac45f41ade29dbdbd30a840e0981b40"
	SIGN_KEY2                 = "STORENEWMIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANYFY/UJGSzhIhpx6YM5KJ9yRHc7YeURxzb9tDvJvMfENHlnP3DtVkOIjERbpsSd76fjtZnMWY60TpGLGyrNkvuV40L15JQhHAo9yURpPQoI0eg3SLFmTEI/MUiPRCwfwYf2deqKKlsmMSysYYHX9JiGzQuWiYZaawxprSuiqDGvAgMBAAECgYEAtQ0QV00gGABISljNMy5aeDBBTSBWG2OjxJhxLRbndZM81OsMFysgC7dq+bUS6ke1YrDWgsoFhRxxTtx/2gDYciGp/c/h0Td5pGw7T9W6zo2xWI5oh1WyTnn0Xj17O9CmOk4fFDpJ6bapL+fyDy7gkEUChJ9+p66WSAlsfUhJ2TECQQD5sFWMGE2IiEuz4fIPaDrNSTHeFQQr/ZpZ7VzB2tcG7GyZRx5YORbZmX1jR7l3H4F98MgqCGs88w6FKnCpxDK3AkEA225CphAcfyiH0ShlZxEXBgIYt3V8nQuc/g2KJtiV6eeFkxmOMHbVTPGkARvt5VoPYEjwPTg43oqTDJVtlWagyQJBAOvEeJLno9aHNExvznyD4/pR4hec6qqLNgMyIYMfHCl6d3UodVvC1HO1/nMPl+4GvuRnxuoBtxj/PTe7AlUbYPMCQQDOkf4sVv58tqslO+I6JNyHy3F5RCELtuMUR6rG5x46FLqqwGQbO8ORq+m5IZHTV/Uhr4h6GXNwDQRh1EpVW0gBAkAp/v3tPI1riz6UuG0I6uf5er26yl5evPyPrjrD299L4Qy/1EIunayC7JYcSGlR01+EDYYgwUkec+QgrRC/NstV"
	SG_KEY1                   = "49ed82335ccdd661"
	HEADER_VALUE_INSTPLATCODE = "109404%2F1093%2F1"
)

var SG_KEY2 = []string{"cab2f5d94d4eea71", "3e8d4f1ce5aa6e09", "c5ca81b0391663db", "517e95007dd031cb", "f8730121dba1cf23"}
var SG_KEY3 = []string{"d9275c219e852eb5e60cb5ceccd523b5", "6bf7d491e5ab9da2931f91eb4c9a1640", "0ca3e6b70c4e0fc4cf53ead33d158e83", "464c6045b59a38069a3e585d8bfe3b90", "f1184b1998cccddd10aa5b8dd039ef8f"}

func MD5(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}
func SHA1(s string) string {
	o := sha1.New()
	o.Write([]byte(s))
	return hex.EncodeToString(o.Sum(nil))
}
func getTargetSignStr(urlStr string, t int64) (str string, err error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return
	}

	str = HEADER_VALUE_OCS + fmt.Sprint(t) + HEADER_VALUE_ID + u.Path + u.RawQuery
	str = SIGN_KEY1 + str + fmt.Sprint(len(str)+48) + SIGN_KEY2
	return
}
func getTargetSgStr(urlStr string, t int64) (str string, err error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return
	}
	str = url.QueryEscape(u.Path+u.RawQuery) + "nullGET" + HEADER_VALUE_ID + fmt.Sprint(t)
	str = strings.ToLower(str)
	return
}

func getSgMethod(targetSgStr, signS string) (str string) {
	sign := targetSgStr + SG_KEY1 + signS
	v26 := sign + fmt.Sprint(len(sign))
	v25 := []byte(v26)
	h := md5.New()
	h.Write(v25)
	v24 := h.Sum(nil)
	v21 := sumX(v24) % 10

	v18 := SG_KEY2[v21%5]
	v17 := SG_KEY3[v21%5]
	sign2 := targetSgStr + v18 + v17
	v5 := sign2 + fmt.Sprint(len(sign2))
	if v21 < 5 { // md5加密
		return MD5(v5)
	} else { // sha1加密
		return SHA1(v5)
	}
}
func sumX(bs []byte) (sum int) {
	for _, b := range bs {
		a := int(b)
		if a > 127 {
			a = 256 - a
		}
		sum += a % 10
	}
	return
}

func DoGetJsonStr(urlStr string) (res string, err error) {
	c := &http.Client{}
	t := time.Now().UnixMilli()
	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		return "", err
	}
	sign, err := getTargetSignStr(urlStr, t)
	sg, err := getTargetSgStr(urlStr, t)
	req.Header.Add(HEADER_KEY_PID, HEADER_VALUE_PID)
	req.Header.Add(HEADER_KEY_USER_AGENT, HEADER_VALUE_USER_AGENT)
	req.Header.Add(HEADER_KEY_T, fmt.Sprint(t))
	req.Header.Add(HEADER_KEY_ID, HEADER_VALUE_ID)
	req.Header.Add(HEADER_KEY_OCS, HEADER_VALUE_OCS)
	req.Header.Add(HEADER_KEY_CH, HEADER_VALUE_CH)
	req.Header.Add(HEADER_KEY_OAK, HEADER_VALUE_OAK)
	req.Header.Add(HEADER_KEY_LOCALE, HEADER_VALUE_LOCALE)
	req.Header.Add(HEADER_KEY_INSTPLATCODE, HEADER_VALUE_INSTPLATCODE)
	req.Header.Add(HEADER_KEY_SIGN, MD5(sign))
	req.Header.Add(HEADER_KEY_SG, getSgMethod(sg, MD5(sign)))

	resp, err := c.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	//log.Println(string(b))
	/*msg := &pb.Response{}
	err = proto.Unmarshal(b, msg)
	if err != nil {
		log.Println(1, err)
		return
	}
	bb, err := json.Marshal(msg)
	if err != nil {
		log.Println(2, err)
		return
	}
	res = string(bb)*/
	res = string(b)
	return
}

type OppoH5Spider struct {
	ChannelName  string
	Platform     string
	TimeInterval int
	AppIconMap   map[string]int
}

func NewOppoH5Driver() (v *OppoH5Spider) {
	v = &OppoH5Spider{
		ChannelName:  "oppo",
		Platform:     "H5",
		AppIconMap:   make(map[string]int),
		TimeInterval: 2 * 60 * 60, //两小时采集间隔
	}
	return
}

func (this OppoH5Spider) Run() {
	go func() {
		this.run()
	}()
}

func (this *OppoH5Spider) Println(v ...interface{}) {
	var str = fmt.Sprintf("[%s]-[%s]", this.ChannelName, this.Platform)
	if conf.Conf.Debug.Switch {
		str += "-[Debug]"
	}
	log.Println(str, fmt.Sprintln(v...))
}

type OppoH5Result struct {
	Code string `json:"code"`
	Msg  string `json:"msg"`
	Data struct {
		CardDtos []struct {
			Games []struct {
				AppId           int    `json:"appId"`           // 小游戏id
				IconUrl         string `json:"iconUrl"`         // 小游戏icon
				Name            string `json:"name"`            // 小游戏名称
				Summary         string `json:"summary"`         // 小游戏简介
				VersionName     string `json:"versionName"`     // 小游戏版本
				FirstOnlineTime int    `json:"firstOnlineTime"` // 上架时间  时间戳。。
				Order           int    `json:"order"`           // 排名
				OnlineCount     int    `json:"onlineCount"`     // 多少人在玩  游戏次数
				GameTags        []struct {
					Name string `json:"name"` // 游戏标签
				} `json:"gameTags"`
			} `json:"games"`
		} `json:"cardDtos"`
	} `json:"data"`
}

// 热门
func (this *OppoH5Spider) collectRankData(date, apiUrl string) (rs db.DbDataResult, err error) {
	jStr, err := DoGetJsonStr(apiUrl)

	var res OppoH5Result
	err = json.Unmarshal([]byte(jStr), &res)
	if res.Code != "2000" {
		this.Println("HTTP Res：", res)
		err = errors.New("collectRankData error,code != 2000")
		return
	}

	rs.Platform = this.Platform
	rs.ChannelName = this.ChannelName
	rs.Date = date

	switch apiUrl {
	case RANK_URL_ONE:
		rs.RankType = "热门榜"
	case RANK_URL_TWO:
		rs.RankType = "新品榜"
	case RANK_URL_THREE:
		rs.RankType = "网游榜"
	}

	var vs []db.ApiData
	for _, d := range res.Data.CardDtos {
		for _, d := range d.Games {
			var appId, typeName, onSaleDate, icon string

			appId = fmt.Sprint(d.AppId) + "-oppoH5"

			for _, t := range d.GameTags {
				typeName += t.Name + ","
			}
			typeName = strings.Trim(typeName, ",")

			if this.AppIconMap[fmt.Sprint(appId)] == 0 {
				icon = d.IconUrl
				this.AppIconMap[fmt.Sprint(appId)] = 1
			}

			onSaleDate = time.Unix(int64(d.FirstOnlineTime/1000), 0).Format("2006-01-02") //设置时间戳 使用模板格式化为日期字符串

			v := db.ApiData{
				AppId:       appId,
				GameDesc:    d.Summary,
				GameName:    d.Name,
				GameVersion: d.VersionName,
				Icon:        icon,
				OnSaleDate:  onSaleDate,
				PlayCount:   d.OnlineCount,
				TypeName:    typeName,
				DIndex:      d.Order - 1, // 1,2,3 改为 0,1,2
				BRank:       1,
			}
			vs = append(vs, v)
		}
	}

	rs.Data = vs

	return
}

func (this *OppoH5Spider) collect(date string) (bool, error) {
	var err error
	this.AppIconMap, err = db.GetAppIconMap(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("db.GetAppIconMap error,err=", err)
		return false, err
	}

	rankTypeList, err := db.QueryRankTypeList(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("collect QueryRankTypeList err:", err)
		return false, err
	}

	// 热门榜
	rs1, err := this.collectRankData(date, RANK_URL_ONE)
	if err != nil {
		this.Println("collect OppoH5Spider 热门榜 err = ", err)
		return false, err
	}
	//获取排行榜ID和表后缀
	rs1.TablePostfix = rankTypeList[rs1.RankType].TablePostfix
	rs1.RankTypeId = rankTypeList[rs1.RankType].Id

	if len(rs1.Data) == 0 {
		this.Println("collectRankData1 长度为空")
	} else {
		err = db.SaveData(rs1)
		if err != nil {
			return false, err
		}
	}

	// 新品榜
	rs2, err := this.collectRankData(date, RANK_URL_TWO)
	if err != nil {
		this.Println("collect OppoH5Spider 新品榜 err = ", err)
		return false, err
	}
	//获取排行榜ID和表后缀
	rs2.TablePostfix = rankTypeList[rs2.RankType].TablePostfix
	rs2.RankTypeId = rankTypeList[rs2.RankType].Id
	if len(rs2.Data) == 0 {
		this.Println("collectRankData2 长度为空")
	} else {
		err = db.SaveData(rs2)
		if err != nil {
			return false, err
		}
	}

	// 网游榜
	rs3, err := this.collectRankData(date, RANK_URL_THREE)
	if err != nil {
		this.Println("collect OppoH5Spider 网游榜 err = ", err)
		return false, err
	}
	//获取排行榜ID和表后缀
	rs3.TablePostfix = rankTypeList[rs3.RankType].TablePostfix
	rs3.RankTypeId = rankTypeList[rs3.RankType].Id
	if len(rs3.Data) == 0 {
		this.Println("collectRankData3 长度为空")
	} else {
		err = db.SaveData(rs3)
		if err != nil {
			return false, err
		}
	}

	return true, nil
}

func (this *OppoH5Spider) run() {
	if conf.Conf.Debug.Switch {
		this.Println("oppoH5 小游戏无法采集之前的数据，禁用调试模式！！！")
	} else {
	re:
		fDate := time.Now().Format("2006-01-02")
		this.GetDataByDate(fDate)
		time.Sleep(time.Duration(this.TimeInterval) * time.Second)
		goto re
	}
}

func (this *OppoH5Spider) GetDataByDate(date string) {
	this.Println("日期:", date, "开始采集数据...")
	succ, err := this.collect(date)
	if succ {
		this.Println("日期:", date, "采集数据完成 稍后再次采集...")
	} else {
		if err == nil {
			this.Println("日期:", date, "数据未更新 稍后重新采集...")
		} else {
			tool.SendWarn("ASO报警-oppoH5爬虫报错", "ASO报警-oppoH5爬虫报错,错误信息："+err.Error())
			this.Println("日期:", date, "出现错误err:", err, "稍后重新采集...")
		}

	}
}
